package com.hatricks.is.redission.processlock;


import com.hatricks.is.redission.ILock;
import com.hatricks.is.redission.ILockKeyEnum;
import com.hatricks.is.redission.KeyLock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Semaphore;

public class LocalKeyLock<K> implements KeyLock<K> {
    // 保存所有锁定的KEY及其信号量
    private final ConcurrentMap<String, Semaphore> map = new ConcurrentHashMap<String, Semaphore>();
    // 保存每个线程锁定的KEY及其锁定计数
    private final ThreadLocal<Map<String, LockInfo>> local = ThreadLocal.withInitial(HashMap::new);

    /**
     * 锁定key，其他等待此key的线程将进入等待，直到调用{@link #unlock}
     * 使用hashcode和equals来判断key是否相同，因此key必须实现{@link #hashCode()}和
     * {@link #equals(Object)}方法
     */
    @Override
    public void lock(K key, String... args) {
        if (key == null) {
            return;
        }
        String lockKey = getLockKey(key, args);
        LockInfo info = local.get().get(lockKey);
        if (info == null) {
            Semaphore current = new Semaphore(1);
            current.acquireUninterruptibly();
            Semaphore previous = map.put(lockKey, current);
            // 在这个map中获取到的信号量是共享的
            if (previous != null) {
                // 当前线程 获取信号量 在此时阻塞
                previous.acquireUninterruptibly();
            }
            local.get().put(lockKey, new LockInfo(current));
        } else {
            info.lockCount++;
        }
    }

    /**
     * 释放key，唤醒其他等待此key的线程
     */
    @Override
    public void unlock(K key, String... args) {
        if (key == null) {
            return;
        }
        String lockKey = getLockKey(key, args);
        LockInfo info = local.get().get(lockKey);
        if (info != null && --info.lockCount == 0) {
            info.current.release();
            map.remove(lockKey, info.current);
            local.get().remove(lockKey);
        }
    }

    @Override
    public ILock getLock(K key, String... args) {
        return null;
    }

    private String getLockKey(K key, String... args) {
        String lock;
        if (key instanceof ILockKeyEnum) {
            lock = getKey(((ILockKeyEnum) key).getKey(), args);
        } else {
            lock = getKey(key.toString(), args);
        }
        return lock;
    }

    private String getKey(String preKey, String... args) {
        StringBuilder key = new StringBuilder(preKey);
        if (args != null) {
            for (String arg : args) {
                key.append(":").append(arg);
            }
        }
        return key.toString();
    }

    private static class LockInfo {
        private final Semaphore current;
        private int lockCount;

        private LockInfo(Semaphore current) {
            this.current = current;
            this.lockCount = 1;
        }
    }
}
